﻿using System;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Xml;
using System.Diagnostics;
using Microsoft.ApplicationBlocks.Updater.Configuration;
using DesignByContract;

namespace Microsoft.ApplicationBlocks.Updater.ActivationProcessors
{
    /// <summary>
    /// Uses the Extract.exe tool from  CAB SDK to extract the files [While preserving directory structure]
    /// 
    /// Source: http://download.microsoft.com/download/platformsdk/cab/2.0/w98nt42kmexp/en-us/Cabsdk.exe
    /// </summary>
    /// <remarks>
    ///     You can use the CABARC.exe to create cab file.
    ///     eg. cabarc -r  -p N myCab.cab myCab\*.*
    ///      Recursively create a cab file [myCab.cab], preserving the directory structure.
    /// <list type="table">
    /// <listheader><term>Attribute</term><description>Description</description></listheader>
    /// <item><term>type</term><description>The type name for this processor</description></item>
    /// <item>
    ///     <term>source</term>
    ///     <description>A reference to a downloaded file as defined by the source attribute in the files list. Relative file path</description>
    ///  </item>
    /// <item><term>destination</term><description>Optional. The filename for the uncompressed file. If not specified the file is not renamed. The path to this file is relative to the application location</description></item>
    /// </list>	
    /// </remarks>
    
    //TODO NH: Add tracing..
    public class ExtractCabProcessor : IActivationProcessor
    {
        private const string APP_SETTINGS_EXPAND_DIR = "Microsoft.ApplicationBlocks.Updater.ActivationProcessors.ExtractCabProcessor.ToolDir";
        private const string ENV_VARIABLE = "PATH";
        private const string CMD_LINE_ARG = "{0} /e *.* /Y /l {1}"; // {0} -Cab File, {1} - Location

        private const string TARGET_EXE = "Extract.exe";

        #region Private members

        /// <summary>
        /// The UpdaterTask provided in the Init method.
        /// </summary>
        private UpdaterTask task;

        /// <summary>
        /// The source file that will be uncompressed.
        /// </summary>
        private string source;

        /// <summary>
        /// The target file after uncompression.
        /// </summary>
        private string destination;

        /// <summary>
        /// The path to the windows tool.
        /// </summary>
        private string utilPath = String.Empty;

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor.
        /// </summary>
        public ExtractCabProcessor()
        {
        }

        #endregion

        #region IActivationProcessor Members

        /// <summary>
        /// Executes the processor.
        /// </summary>
        public void Execute()
        {

            if (destination != null && destination.Length > 0 && !Path.IsPathRooted(destination))
            {
                destination = Path.Combine(task.Manifest.Application.Location, destination);
            }

            if (!Path.IsPathRooted(source))
            {
                source = Path.Combine(task.DownloadFilesBase, source);
            }

            using (Process expandProcess = new Process())
            {
                expandProcess.StartInfo.FileName = utilPath;
                if (destination != null && destination.Length > 0)
                {
                    expandProcess.StartInfo.Arguments 
                        = String.Format(CultureInfo.InvariantCulture, CMD_LINE_ARG, source, Path.GetFullPath( destination));
                }
                else
                {
                    expandProcess.StartInfo.Arguments
                        = String.Format(CultureInfo.InvariantCulture, CMD_LINE_ARG, source, Path.GetFullPath(task.Manifest.Application.Location));
                }

                Logger.LogInformation("Calling Extract.exe with args: {0}", expandProcess.StartInfo.Arguments);

                expandProcess.Start();
                expandProcess.WaitForExit();
                if (expandProcess.ExitCode != 0)
                {
                    throw new Exception(Resource.ResourceManager[Resource.MessageKey.ToolGeneratedError, TARGET_EXE, expandProcess.ExitCode]);
                }
            }
        }

        /// <summary>
        /// Initializes the processor using the manifest configuration and the UpdaterTask instance.
        /// </summary>
        /// <param name="config">The configuration for the processor in the manifest file.</param>
        /// <param name="task">The UpdaterTask instance.</param>
        public void Init(ActivationProcessorProviderData config, UpdaterTask task)
        {
            this.task = task;

            if (config.AnyAttributes == null)
            {
                throw new ConfigurationErrorsException(Resource.ResourceManager[Resource.MessageKey.ProcessorNotConfigured]);
            }

            foreach (XmlAttribute attr in config.AnyAttributes)
            {
                if (String.Compare(attr.Name, "source", false, CultureInfo.InvariantCulture) == 0)
                {
                    source = attr.Value;
                }
                if (String.Compare(attr.Name, "destination", false, CultureInfo.InvariantCulture) == 0)
                {
                    destination = attr.Value;
                }
            }

            if (source != null && source.Length == 0)
            {
                throw new ArgumentException(Resource.ResourceManager[Resource.MessageKey.SourceAndDestinationExpected]);
            }

            if (String.IsNullOrEmpty(destination))
            {
                destination = ".";
            }
        }

        /// <summary>
        /// If the activation fails this method is called to revert the operations performed by the processor.
        /// </summary>
        public void OnError()
        {
        }

        /// <summary>
        /// Prepares for the execution.
        ///  [Second step, called after Init]
        /// Adds the path to the Expand.exe. 
        /// </summary>
        public void PrepareExecution()
        {
            string expandToolLocation = AppSettingsHelper.Retrieve<string>(APP_SETTINGS_EXPAND_DIR);
            utilPath = Path.Combine(expandToolLocation, TARGET_EXE);

            CheckFileExistance(utilPath);
            
            //Add to Environment Variables
            Environment.SetEnvironmentVariable(ENV_VARIABLE,
                    String.Format("{0};{1};", Environment.GetEnvironmentVariable(ENV_VARIABLE), expandToolLocation)
                    );

            CheckFileExistance(GetSourceFilePath());
        }

        #endregion

        private string GetSourceFilePath()
        {
            return Path.Combine(task.DownloadFilesBase, source);
        }

        private void CheckFileExistance(string filePath)
        {
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(Resource.ResourceManager[Resource.MessageKey.CannotFindTool, filePath]);
            }
        }
    }

}
